package com.pig4cloud.pigx.contract.utils;

import cn.hutool.core.util.ArrayUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.mongodb.client.result.UpdateResult;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.customize.api.enums.AbnormalEnum;
import com.pig4cloud.pigx.common.customize.api.util.OpenApiUtil;
import com.pig4cloud.pigx.common.customize.contants.BigDecimalConstants;
import com.pig4cloud.pigx.common.customize.contants.MongoTableNameConstants;
import com.pig4cloud.pigx.common.customize.enums.CommonDictEnum;
import com.pig4cloud.pigx.common.customize.exception.MqAckException;
import com.pig4cloud.pigx.common.customize.exception.MqNotAckException;
import com.pig4cloud.pigx.common.customize.i18n.util.MessageUtils;
import com.pig4cloud.pigx.common.customize.mongo.utils.MongoUtils;
import com.pig4cloud.pigx.common.customize.security.utils.SecurityUtil;
import com.pig4cloud.pigx.common.customize.utils.ReflectUtil;
import com.pig4cloud.pigx.contract.annotation.SetScaleMainCurrency;
import com.pig4cloud.pigx.contract.annotation.SetScaleTokenCurrency;
import com.pig4cloud.pigx.contract.bo.ContractExistPositionInfoBO;
import com.pig4cloud.pigx.contract.bo.ContractTradeNewBO;
import com.pig4cloud.pigx.contract.bo.RateSettlementJobBO;
import com.pig4cloud.pigx.contract.constant.ContractKLineConstants;
import com.pig4cloud.pigx.contract.constant.ContractMqConstants;
import com.pig4cloud.pigx.contract.constant.ContractRedisKeyConstants;
import com.pig4cloud.pigx.contract.constant.I18nMessages;
import com.pig4cloud.pigx.contract.constant.OrderExecutorErrorMsg;
import com.pig4cloud.pigx.contract.dto.ContractHierarchyDTO;
import com.pig4cloud.pigx.contract.dto.ContractOrderCancelDTO;
import com.pig4cloud.pigx.contract.dto.ContractOrderNewDTO;
import com.pig4cloud.pigx.contract.dto.ContractPositionLiquidateEmailDTO;
import com.pig4cloud.pigx.contract.dto.ContractPositionMarginAppendDTO;
import com.pig4cloud.pigx.contract.dto.ContractPositionNeedAppendMarginEmailDTO;
import com.pig4cloud.pigx.contract.entity.ContractKLine;
import com.pig4cloud.pigx.contract.entity.ContractOrder;
import com.pig4cloud.pigx.contract.entity.ContractPosition;
import com.pig4cloud.pigx.contract.entity.ContractPositionMarginDetail;
import com.pig4cloud.pigx.contract.entity.ContractSymbol;
import com.pig4cloud.pigx.contract.entity.RippleContractSymbolHierarchy;
import com.pig4cloud.pigx.contract.enums.ContractOrderEnum;
import com.pig4cloud.pigx.contract.enums.ContractPositionEnum;
import com.pig4cloud.pigx.contract.enums.ContractPositionMarginDetailEnum;
import com.pig4cloud.pigx.contract.executer.order.supporter.OrderCalculator;
import com.pig4cloud.pigx.contract.executer.position.supporter.PositionCalculator;
import com.pig4cloud.pigx.contract.mapper.ContractKLineMapper;
import com.pig4cloud.pigx.contract.mapper.ContractOrderMapper;
import com.pig4cloud.pigx.contract.mapper.ContractPositionMapper;
import com.pig4cloud.pigx.contract.mapper.ContractSymbolMapper;
import com.pig4cloud.pigx.contract.mapper.RippleContractSymbolHierarchyMapper;
import com.pig4cloud.pigx.contract.mapstruct.ContractSymbolStruct;
import com.pig4cloud.pigx.contract.mongo.entity.ContractKLineMongo;
import com.pig4cloud.pigx.contract.mongo.entity.ContractKLineTradeCacheMongo;
import com.pig4cloud.pigx.contract.mongo.entity.ContractOrderMongo;
import com.pig4cloud.pigx.contract.mongo.entity.ContractSymbolHierarchyMongo;
import com.pig4cloud.pigx.contract.mongo.entity.ContractSymbolMongo;
import com.pig4cloud.pigx.contract.mongo.entity.ContractTradeMongo;
import com.pig4cloud.pigx.contract.open.api.contants.OpenApiRedisConstantsV1;
import com.pig4cloud.pigx.contract.vo.AppContractCurrentPositionVO;
import com.pig4cloud.pigx.contract.vo.ContractHierarchyVO;
import com.pig4cloud.pigx.contract.vo.ContractLiquidateOrderVO;
import com.pig4cloud.pigx.robot.bo.RobotDepthMapMqBO;
import com.pig4cloud.pigx.user.entity.User;
import com.pig4cloud.pigx.wallet.api.feign.RemoteRippleUserService;
import com.pig4cloud.pigx.wallet.api.feign.WalletContractAccountControllerFeign;
import com.pig4cloud.pigx.wallet.dto.WalletContractAccountChangeDTO;
import com.pig4cloud.pigx.wallet.entity.ContractAccount;
import com.pig4cloud.pigx.wallet.enums.WalletContractAccountDetailEnum;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Nullable;
import javax.mail.MessagingException;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 合约模块工具类
 *
 * @since 2022/8/16 20:05
 */
@Slf4j
@Component
@AllArgsConstructor
public class ContractUtils {

    private final WalletContractAccountControllerFeign walletContractAccountControllerFeign;
    private final RemoteRippleUserService remoteRippleUserService;

    private final RippleContractSymbolHierarchyMapper hierarchyMapper;
    private final ContractSymbolMapper contractSymbolMapper;
    private final ContractOrderMapper contractOrderMapper;
    private final ContractPositionMapper contractPositionMapper;
    private final ContractKLineMapper lineMapper;

    private final MongoTemplate mongo;
    private final RedisTemplate redis;
    private final AmqpTemplate rabbit;

    private final SecurityUtil securityUtil;
    private final ContractEmailSender contractEmailSender;

    public static ThreadLocal<Map<String, ContractSymbolMongo>> symbolMap = new ThreadLocal<>();

    /**
     * 根据id查询用户
     *
     * @param userId 用户id
     * @return {@link User}
     * @since 2022/9/14 18:24
     */
    public User getUserById(Long userId) {
        R<User> r = remoteRippleUserService.getUserByUserId(userId, SecurityConstants.FROM_IN);
        return r.getData();
    }

    /**
     * 根据id查询用户
     *
     * @param userId 用户id
     * @return {@link User}
     * @since 2022/9/14 18:24
     */
    public User getUserByIdNotNull(Long userId) {
        User user = getUserById(userId);
        if (Objects.isNull(user)) {
            throw new MqAckException("未能查询到用户信息");
        }

        return user;
    }

    /**
     * 根据id从mongo、mysql中获取币对信息
     *
     * @param id id
     * @return {@link ContractSymbolMongo}
     * @since 2022/8/16 20:09
     */
    public ContractSymbolMongo getSymbolById(Long id) {
        if (Objects.isNull(id)) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.SYSMBOL_ID_IS_EMPTY));
        }

        // 从mongo中查询
        Criteria criteria = Criteria.where("id").is(id);
        Query query = Query.query(criteria);

        ContractSymbolMongo symbolMongo = mongo.findOne(query, ContractSymbolMongo.class);
        if (!Objects.isNull(symbolMongo)) {
            return symbolMongo;
        }

        // 如果缓存没有，从mysql中查询
        QueryWrapper<ContractSymbol> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda()
                .eq(ContractSymbol::getId, id)
                .eq(ContractSymbol::getStatus, CommonDictEnum.Status.NORMAL.getValue());
        ContractSymbol symbolMysql = contractSymbolMapper.selectOne(queryWrapper);
        if (Objects.isNull(symbolMysql)) {
            // 数据异常，币对信息已不可用
            throw new RuntimeException(MessageUtils.get(I18nMessages.CURRENCY_PAIR_INFORMAION_IS_NOT_AVAILABLE));
        }
        symbolMongo = ContractSymbolStruct.INSTANCE.entity2Mongo(symbolMysql);

        // 更新缓存
        Update update = MongoUtils.getUpdate(symbolMongo);
        mongo.upsert(query, update, ContractSymbolMongo.class);

        return symbolMongo;
    }

    /**
     * 根据id从mongo、mysql中获取币对信息
     *
     * @param symbolName 币对名称
     * @return {@link ContractSymbolMongo}
     * @since 2022/8/16 20:09
     */
    public ContractSymbolMongo getSymbolByName(String symbolName) {
        if (Strings.isEmpty(symbolName)) {
            throw new RuntimeException("数据异常，symbolName为空");
        }

        // 从mongo中查询
        Criteria criteria = Criteria.where("name").is(symbolName);
        Query query = Query.query(criteria);

        ContractSymbolMongo symbolMongo = mongo.findOne(query, ContractSymbolMongo.class);
        if (!Objects.isNull(symbolMongo)) {
            return symbolMongo;
        }

        // 如果缓存没有，从mysql中查询
        QueryWrapper<ContractSymbol> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda()
                .eq(ContractSymbol::getName, symbolName)
                .eq(ContractSymbol::getStatus, CommonDictEnum.Status.NORMAL.getValue());
        ContractSymbol symbolMysql = contractSymbolMapper.selectOne(queryWrapper);
        if (Objects.isNull(symbolMysql)) {
            throw new RuntimeException("数据异常，币对信息已不可用：" + symbolName);
        }
        symbolMongo = ContractSymbolStruct.INSTANCE.entity2Mongo(symbolMysql);

        // 更新缓存
        Update update = MongoUtils.getUpdate(symbolMongo);
        mongo.upsert(query, update, ContractSymbolMongo.class);

        return symbolMongo;
    }

    /**
     * 校验币对信息
     *
     * @param symbol 币对信息
     * @since 2022/8/16 20:09
     */
    public static void checkSymbol(ContractSymbolMongo symbol) {
        if (Objects.isNull(symbol)) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.FAILED_TO_OBTAIN_COIN_PAIR_INFORMATION));
        }

        if (Objects.isNull(symbol.getMarkPrice())) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.FAILED_TO_OBTAIN_THE_TAG_PRICE));
        }

        if (Objects.isNull(symbol.getLastPrice())) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.FAILED_TO_OBTAIN_THE_LATEST_PRICE));
        }

        if (Objects.isNull(symbol.getIndexPrice())) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.FAILED_TO_OBTAIN_THE_INDEX_PRICE));
        }
    }

    /**
     * 根据ids从mongo、mysql中获取币对信息
     *
     * @param ids ids
     * @return {@link List<ContractSymbolMongo>}
     * @since 2022/8/16 20:09
     */
    public List<ContractSymbolMongo> getSymbolByIds(Long... ids) {
        if (ArrayUtil.isEmpty(ids)) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.DATA_IS_EMPTY, "symbolIds"));
        }

        // 从mongo中查询
        Criteria criteria = Criteria.where("id").in((Object[]) ids);
        Query query = Query.query(criteria);

        return mongo.find(query, ContractSymbolMongo.class);
    }

    /**
     * 从mongo、mysql中获取币对信息
     *
     * @return {@link List<ContractSymbolMongo>}
     * @since 2022/8/16 20:09
     */
    public List<ContractSymbolMongo> getSymbolList() {
        // 从mongo中查询
        List<ContractSymbolMongo> listMongo = mongo.findAll(ContractSymbolMongo.class);
        if (!CollectionUtils.isEmpty(listMongo)) {
            return listMongo;
        }

        // 如果缓存没有，从mysql中查询
        QueryWrapper<ContractSymbol> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda()
                .eq(ContractSymbol::getStatus, CommonDictEnum.Status.NORMAL.getValue());
        List<ContractSymbol> listMysql = contractSymbolMapper.selectList(queryWrapper);
        if (CollectionUtils.isEmpty(listMysql)) {
            return listMongo;
        }
        listMongo = ContractSymbolStruct.INSTANCE.list2Mongo(listMysql);

        // 更新缓存
        for (ContractSymbolMongo symbol : listMongo) {
            Criteria criteria = Criteria.where("id").is(symbol.getId());
            Query query = Query.query(criteria);
            Update update = MongoUtils.getUpdate(listMongo);
            mongo.upsert(query, update, ContractSymbolMongo.class);
        }

        return listMongo;
    }

    /**
     * 根据id查询仓位信息
     *
     * @param id   id
     * @param lock 是否锁定
     * @return {@link ContractPosition}
     * @since 2022/8/16 20:09
     */
    public ContractPosition getPositionById(@NotNull Long id, boolean lock) {
        if (Objects.isNull(id)) {
            throw new RuntimeException("数据异常，positionId为空");
        }

        QueryWrapper<ContractPosition> query = new QueryWrapper<>();
        query.lambda()
                .eq(ContractPosition::getId, id)
                .last(lock, "for update");

        ContractPosition position = contractPositionMapper.selectOne(query);
        if (Objects.isNull(position)) {
            throw new RuntimeException("获取仓位信息失败");
        }

        return position;
    }

    /**
     * 根据id查询订单信息
     *
     * @param id id
     * @return {@link ContractOrder}
     * @since 2022/8/16 20:09
     */
    public ContractOrder getOrderById(Long id) {
        if (Objects.isNull(id)) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.DATA_IS_EMPTY, "orderId"));
        }

        ContractOrder order = contractOrderMapper.selectById(id);
        if (Objects.isNull(order)) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.FAILED_TO_OBTAIN_ORDER_INFORMATION));
        }

        return order;
    }

    /**
     * 增加挂单数量计数
     *
     * @param userId 用户id
     * @param step   步数
     * @return {@link Long}
     * @since 2022/8/18 10:33
     */
    public Long incrOrderPending(Long userId, int step) {
        // log.info(step > 0 ? "挂单数量计数++" : "挂单数量计数--");
        return redis.opsForHash().increment(ContractRedisKeyConstants.ORDER_PENDING_COUNT, userId.toString(), step);
    }

    /**
     * 增加条件单挂单数量计数
     *
     * @param step   步数
     * @param userId 用户id
     * @return {@link Long}
     * @since 2022/8/18 10:33
     */
    public Long incrOrderConditionPending(Long userId, int step) {
        // log.info(step > 0 ? "条件单挂单数量计数++" : "条件单挂单数量计数--");
        return redis.opsForHash().increment(ContractRedisKeyConstants.ORDER_CONDITION_PENDING_COUNT, userId.toString(), step);
    }

    /**
     * 增加总持仓数量
     *
     * @param userId     用户id
     * @param symbolName 币对名称
     * @param step       步数
     * @return {@link double}
     * @since 2022/8/18 10:33
     */
    public double incrPositionTotalNum(Long userId, String symbolName, BigDecimal step) {
        // log.info(step.compareTo(BigDecimal.ZERO) > 0 ? "总持仓数量计数++" : "总持仓数量计数--");
        String hashKey = userId.toString() + ":" + symbolName;
        return redis.opsForHash().increment(ContractRedisKeyConstants.POSITION_TOTAL_NUM, hashKey, step.doubleValue());
    }

    /**
     * 是否是条件单挂单
     *
     * @param hasStopProfit 是否止盈
     * @param hasStopLost   是否止损
     * @return {@link boolean}
     * @since 2022/8/18 10:48
     */
    public static boolean isConditionPending(String hasStopProfit, String hasStopLost) {
        return Objects.equals(CommonDictEnum.YesOrNo.YES.getValue(), hasStopProfit)
                || Objects.equals(CommonDictEnum.YesOrNo.YES.getValue(), hasStopLost);
    }

    /**
     * 判断是否需要修改挂单数量
     *
     * @param type               订单类型
     * @param isConditionPending 是否是条件挂单
     * @return {@link boolean}
     * @since 2022/8/18 10:48
     */
    public static boolean isNeedChangePending(String type, boolean isConditionPending) {
        return Objects.equals(ContractOrderEnum.Type.LIMIT.getValue(), type)
                || (Objects.equals(ContractOrderEnum.Type.MARKET.getValue(), type) && isConditionPending);
    }


    /**
     * 根据是买单还是买单，获取对应的订单薄
     *
     * @param isBuy 是否是买单
     * @return {@link String}
     * @since 2022/8/18 22:24
     */
    public static String getOrderBookName(String isBuy) {
        return Objects.equals(CommonDictEnum.YesOrNo.YES.getValue(), isBuy) ?
                MongoTableNameConstants.CONTRACT_ORDER_BOOK_BUY
                : MongoTableNameConstants.CONTRACT_ORDER_BOOK_SALE;
    }

    /**
     * 根据是买单还是买单，获取对手盘的订单薄
     *
     * @param isBuy 是否是买单
     * @return {@link String}
     * @since 2022/8/18 22:24
     */
    public static String getOpponentOrderBookName(String isBuy) {
        return Objects.equals(CommonDictEnum.YesOrNo.YES.getValue(), isBuy) ?
                MongoTableNameConstants.CONTRACT_ORDER_BOOK_SALE
                : MongoTableNameConstants.CONTRACT_ORDER_BOOK_BUY;
    }

    /**
     * 根据订单类型获得手续费率
     *
     * @param orderType 订单类型
     * @param symbol    币对信息
     * @return {@link BigDecimal}
     * @since 2022/8/19 17:48
     */
    public static BigDecimal getHandlingFeeRate(String orderType, ContractSymbolMongo symbol) {
        if (Objects.equals(ContractOrderEnum.Type.MARKET.getValue(), orderType)) {
            return symbol.getMarketFeeRate();
        } else if (Objects.equals(ContractOrderEnum.Type.LIMIT.getValue(), orderType)
                || Objects.equals(ContractOrderEnum.Type.LIQUIDATE.getValue(), orderType)) {
            return symbol.getLimitFeeRate();
        }

        throw new RuntimeException(MessageUtils.get(I18nMessages.UNRECOGNIZED_ORDER_INFORMATION));
    }

    /**
     * 查询一个当前仓位
     *
     * @param condition 查询条件
     * @param lock      是否锁定
     * @return {@link ContractPosition}
     * @since 2022/8/19 17:52
     */
    public ContractPosition getOneCurrentPosition(ContractPosition condition, boolean lock) {
        QueryWrapper<ContractPosition> query = new QueryWrapper<>();
        query.lambda()
                .eq(!Objects.isNull(condition.getId()), ContractPosition::getId, condition.getId())
                .eq(!Objects.isNull(condition.getUserId()), ContractPosition::getUserId, condition.getUserId())
                .eq(!Objects.isNull(condition.getSymbolId()), ContractPosition::getSymbolId, condition.getSymbolId())
                .eq(!Objects.isNull(condition.getSymbolName()), ContractPosition::getSymbolName, condition.getSymbolName())
                .eq(Strings.isNotEmpty(condition.getMode()), ContractPosition::getMode, condition.getMode())
                .eq(Strings.isNotEmpty(condition.getSide()), ContractPosition::getSide, condition.getSide())
                .in(ContractPosition::getStatus,
                        ContractPositionEnum.Status.OPEN.getValue(),
                        ContractPositionEnum.Status.PARTIAL_CLOSE.getValue(),
                        ContractPositionEnum.Status.LIQUIDATING.getValue())
                .last("limit 1")
                .last(lock, "for update");
        return contractPositionMapper.selectOne(query);
    }

    /**
     * 查询所有符合条件的当前仓位
     *
     * @param condition 查询条件
     * @param lock      锁定
     * @return {@link List<ContractPosition>}
     * @since 2022/8/19 17:52
     */
    public List<ContractPosition> getAllCurrentPosition(ContractPosition condition, boolean lock) {
        QueryWrapper<ContractPosition> query = new QueryWrapper<>();
        query.lambda()
                .eq(!Objects.isNull(condition.getId()), ContractPosition::getId, condition.getId())
                .eq(!Objects.isNull(condition.getUserId()), ContractPosition::getUserId, condition.getUserId())
                .eq(!Objects.isNull(condition.getSymbolId()), ContractPosition::getSymbolId, condition.getSymbolId())
                .eq(Strings.isNotEmpty(condition.getMode()), ContractPosition::getMode, condition.getMode())
                .eq(Strings.isNotEmpty(condition.getSide()), ContractPosition::getSide, condition.getSide())
                .in(ContractPosition::getStatus,
                        ContractPositionEnum.Status.OPEN.getValue(),
                        ContractPositionEnum.Status.PARTIAL_CLOSE.getValue(),
                        ContractPositionEnum.Status.LIQUIDATING.getValue())
                .last(lock, "for update");
        return contractPositionMapper.selectList(query);
    }

    /**
     * 查询一个符合条件的可用仓位
     *
     * @param condition 查询条件
     * @param lock      是否锁定
     * @return {@link ContractPosition}
     * @since 2022/8/19 17:52
     */
    public ContractPosition getOneAvailablePosition(ContractPosition condition, boolean lock) {
        QueryWrapper<ContractPosition> query = new QueryWrapper<>();
        query.lambda()
                .eq(!Objects.isNull(condition.getId()), ContractPosition::getId, condition.getId())
                .eq(!Objects.isNull(condition.getUserId()), ContractPosition::getUserId, condition.getUserId())
                .eq(!Objects.isNull(condition.getSymbolId()), ContractPosition::getSymbolId, condition.getSymbolId())
                .eq(Strings.isNotEmpty(condition.getMode()), ContractPosition::getMode, condition.getMode())
                .eq(Strings.isNotEmpty(condition.getSide()), ContractPosition::getSide, condition.getSide())
                .eq(ContractPosition::getNeedLiquidate, CommonDictEnum.YesOrNo.NO.getValue())
                .in(ContractPosition::getStatus,
                        ContractPositionEnum.Status.OPEN.getValue(),
                        ContractPositionEnum.Status.PARTIAL_CLOSE.getValue())
                .last("limit 1")
                .last(lock, "for update");
        return contractPositionMapper.selectOne(query);
    }

    /**
     * 查询所有符合条件的可用仓位
     *
     * @param condition 查询条件
     * @param lock      锁定
     * @return {@link List<ContractPosition>}
     * @since 2022/8/19 17:52
     */
    public List<ContractPosition> getAllAvailablePosition(ContractPosition condition, boolean lock) {
        QueryWrapper<ContractPosition> query = new QueryWrapper<>();
        query.lambda()
                .eq(!Objects.isNull(condition.getId()), ContractPosition::getId, condition.getId())
                .eq(!Objects.isNull(condition.getUserId()), ContractPosition::getUserId, condition.getUserId())
                .eq(!Objects.isNull(condition.getSymbolId()), ContractPosition::getSymbolId, condition.getSymbolId())
                .eq(Strings.isNotEmpty(condition.getMode()), ContractPosition::getMode, condition.getMode())
                .eq(Strings.isNotEmpty(condition.getSide()), ContractPosition::getSide, condition.getSide())
                .eq(ContractPosition::getNeedLiquidate, CommonDictEnum.YesOrNo.NO.getValue())
                .in(ContractPosition::getStatus,
                        ContractPositionEnum.Status.OPEN.getValue(),
                        ContractPositionEnum.Status.PARTIAL_CLOSE.getValue())
                .eq(!Objects.isNull(condition.getMarginCheckStatus()), ContractPosition::getMarginCheckStatus, condition.getMarginCheckStatus())
                .last(lock, "for update");
        return contractPositionMapper.selectList(query);
    }

    /**
     * 查询一个符合条件的可用订单
     *
     * @param condition 查询条件
     * @param lock      是否锁定
     * @return {@link ContractPosition}
     * @since 2022/8/19 17:52
     */
    public ContractOrder getOneAvailableOrder(ContractOrder condition, boolean lock) {
        QueryWrapper<ContractOrder> query = new QueryWrapper<>();
        query.lambda()
                .eq(!Objects.isNull(condition.getId()), ContractOrder::getId, condition.getId())
                .eq(!Objects.isNull(condition.getUserId()), ContractOrder::getUserId, condition.getUserId())
                .eq(!Objects.isNull(condition.getSymbolId()), ContractOrder::getSymbolId, condition.getSymbolId())
                .eq(Strings.isNotEmpty(condition.getSide()), ContractOrder::getSide, condition.getSide())
                .in(ContractOrder::getStatus,
                        ContractOrderEnum.Status.ON_GOING.getValue(),
                        ContractOrderEnum.Status.PARTIAL_DEAL.getValue())
                .last("limit 1")
                .last(lock, "for update");
        return contractOrderMapper.selectOne(query);
    }

    /**
     * 查询所有符合条件的可用订单
     *
     * @param condition 查询条件
     * @param lock      是否锁定
     * @return {@link List< ContractOrder>}
     * @since 2022/8/19 17:52
     */
    public List<ContractOrder> getAllAvailableOrder(ContractOrder condition, boolean lock) {
        QueryWrapper<ContractOrder> query = new QueryWrapper<>();
        query.lambda()
                .eq(!Objects.isNull(condition.getId()), ContractOrder::getId, condition.getId())
                .eq(!Objects.isNull(condition.getUserId()), ContractOrder::getUserId, condition.getUserId())
                .eq(!Objects.isNull(condition.getSymbolId()), ContractOrder::getSymbolId, condition.getSymbolId())
                .eq(Strings.isNotEmpty(condition.getSide()), ContractOrder::getSide, condition.getSide())
                .in(ContractOrder::getStatus,
                        ContractOrderEnum.Status.ON_GOING.getValue(),
                        ContractOrderEnum.Status.PARTIAL_DEAL.getValue())
                .last(lock, "for update");
        return contractOrderMapper.selectList(query);
    }

    /**
     * 组装contractUtils.getCurrentPosition()查询条件
     *
     * @param id       id
     * @param userId   用户id
     * @param symbolId 币对列表
     * @param mode     仓位模式
     * @param side     方向
     * @return {@link ContractPosition}
     * @since 2022/8/23 10:04
     */
    public static ContractPosition getCurrentPositionCondition(Long id,
                                                               Long userId,
                                                               Long symbolId,
                                                               String mode,
                                                               String side) {
        ContractPosition condition = new ContractPosition();
        condition.setId(id);
        condition.setUserId(userId);
        condition.setSymbolId(symbolId);
        condition.setMode(mode);
        condition.setSide(side);
        return condition;
    }

    /**
     * 查询用户已有的仓位信息
     *
     * @param userId   用户id
     * @param symbolId 币对id
     * @return {@link ContractExistPositionInfoBO}
     * @since 2022/8/21 13:44
     */
    public ContractExistPositionInfoBO getExistPositionInfo(Long userId, Long symbolId) {
        // 根据币种，查询当前仓位信息
        ContractExistPositionInfoBO existInfo = contractPositionMapper.existInfo(userId, symbolId);
        // 查询币种信息
        ContractSymbolMongo symbol = mongo.findOne(Query.query(Criteria.where("_id").is(symbolId)), ContractSymbolMongo.class, MongoTableNameConstants.CONTRACT_SYMBOL);
        if (!Objects.isNull(existInfo)) {
            // 标记信息来自仓位
            existInfo.setHasPosition(true);

            if (!Objects.isNull(symbol)) {
                existInfo.setMaxMultiple(symbol.getMultiple());
                try {
                    // 查询头寸信息
                    List<ContractSymbolHierarchyMongo> hierarchyList = mongo.find(Query.query(Criteria.where("symbolId").is(symbolId)),
                            ContractSymbolHierarchyMongo.class);
                    existInfo.setSymbolHierarchy(hierarchyList);
                } catch (Exception ignored) {
                    existInfo.setSymbolHierarchy(Collections.emptyList());
                }
            }
            return existInfo;
        }

        // 为空，创建对象
        existInfo = new ContractExistPositionInfoBO();
        try {
            // 查询头寸信息
            List<ContractSymbolHierarchyMongo> hierarchyList = mongo.find(Query.query(Criteria.where("symbolId").is(symbolId)),
                    ContractSymbolHierarchyMongo.class);
            existInfo.setSymbolHierarchy(hierarchyList);
        } catch (Exception ignored) {
            existInfo.setSymbolHierarchy(Collections.emptyList());
        }
        if (!Objects.isNull(symbol)) {
            existInfo.setMaxMultiple(symbol.getMultiple());
        }

        // 如果没能查询到仓位，组装mongo查询条件
        Criteria criteria = Criteria.where("userId").is(userId)
                .and("symbolId").is(symbolId);

        Query query = Query.query(criteria)
                .skip(0)
                .limit(1);
        query.fields().include("positionMode", "multiple");
        // 根据创建时间倒序
        query.with(Sort.by(Sort.Order.desc("createTime")));

        // 查询 contract_order_book_sale 中最新的仓位模式信息
        ContractOrderMongo orderBookSale = mongo.findOne(query, ContractOrderMongo.class, MongoTableNameConstants.CONTRACT_ORDER_BOOK_SALE);
        if (!Objects.isNull(orderBookSale)) {
            existInfo.setMode(orderBookSale.getPositionMode());
            existInfo.setMultiple(orderBookSale.getMultiple());
        }

        // 依然为空，查询 contract_order_book_buy 中最新的仓位模式信息
        if (Strings.isEmpty(existInfo.getMode())) {
            ContractOrderMongo orderBookBuy = mongo.findOne(query, ContractOrderMongo.class, MongoTableNameConstants.CONTRACT_ORDER_BOOK_BUY);
            if (!Objects.isNull(orderBookBuy)) {
                existInfo.setMode(orderBookBuy.getPositionMode());
                existInfo.setMultiple(orderBookBuy.getMultiple());
            }
        }

        // 依然为空，查询 contract_trade_new 中最新的仓位模式信息
        if (Strings.isEmpty(existInfo.getMode())) {
            ContractTradeMongo tradeNew = mongo.findOne(query, ContractTradeMongo.class, MongoTableNameConstants.CONTRACT_TRADE_NEW);
            if (!Objects.isNull(tradeNew)) {
                existInfo.setMode(tradeNew.getPositionMode());
                existInfo.setMultiple(tradeNew.getMultiple());
            }
        }

        return existInfo;
    }

    /**
     * 校验仓位状态
     *
     * @param position 仓位信息
     * @param nullable 是否可为空
     * @since 2022/8/23 09:50
     */
    public static void checkCurrentPosition(ContractPosition position, boolean nullable) {
        if (Objects.isNull(position)) {
            if (nullable) {
                return;
            } else {
                throw new MqAckException(MessageUtils.get(I18nMessages.POSITION_INFORMATION_HAS_CHANGED));
            }
        }

        // 是否是需要强平
        boolean needLiquidate = Objects.equals(CommonDictEnum.YesOrNo.YES.getValue(), position.getNeedLiquidate());
        if (needLiquidate) {
            throw new MqAckException(MessageUtils.get(I18nMessages.POSITION_IS_BEING_STRONGLY_CLOSED));
        }
    }

    /**
     * 判断是否是买单
     * <p>买单：做多开仓、做空平仓
     * <p>卖单：做多平仓、做空开仓
     *
     * @param operationType 操作类型（0开仓、1平仓）
     * @param side          方向（0做多、1做空）
     * @since 2022/8/18 16:47
     */
    @Validated
    public static String judgeIsBuy(@NotBlank String operationType,
                                    @NotBlank String side) {
        if (Strings.isEmpty(operationType)) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.DATA_IS_EMPTY, "positionId"));
        }
        if (Strings.isEmpty(side)) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.DATA_IS_EMPTY, "side"));
        }

        if (Objects.equals(ContractOrderEnum.OperationType.OPEN.getValue(), operationType)) {
            // 做多开仓
            if (Objects.equals(ContractOrderEnum.Side.MORE.getValue(), side)) {
                return CommonDictEnum.YesOrNo.YES.getValue();
            } else if (Objects.equals(ContractOrderEnum.Side.EMPTY.getValue(), side)) {
                // 做空开仓
                return CommonDictEnum.YesOrNo.NO.getValue();
            }

        } else if (Objects.equals(ContractOrderEnum.OperationType.CLOSE.getValue(), operationType)) {
            // 做多平仓
            if (Objects.equals(ContractOrderEnum.Side.MORE.getValue(), side)) {
                return CommonDictEnum.YesOrNo.NO.getValue();
            } else if (Objects.equals(ContractOrderEnum.Side.EMPTY.getValue(), side)) {
                // 做空平仓
                return CommonDictEnum.YesOrNo.YES.getValue();
            }
        }

        throw new RuntimeException(MessageUtils.get(I18nMessages.IMPOSSIBLE_TO_TELL_WHETHER_IT_IS_THE_PURCHASE));
    }

    /**
     * 改变orderBook中isNew为false
     *
     * @param order 订单
     * @since 2022/8/23 11:45
     */
    public void changeIsNew(ContractOrderMongo order) {
        if (order.getIsRobot().equals(CommonDictEnum.YesOrNo.NO.getValue())) {
            String orderBookName = ContractUtils.getOrderBookName(order.getIsBuy());
            Update update = Update.update("isNew", false);
            Criteria criteria = Criteria.where("id").is(order.getId());
            UpdateResult updateResult = mongo.updateFirst(Query.query(criteria),
                    update,
                    ContractOrderMongo.class,
                    orderBookName);

            if (updateResult.getModifiedCount() != 1) {
                String errorMsg = String.format("修改订单isNew状态为false失败，id：%s", order.getId());
                log.error(OrderExecutorErrorMsg.ORDER_MATCH_ERROR, errorMsg);
            }
        }
    }

    /**
     * 获取指定 k线的最新数据
     *
     * @param symbolName 币对名称
     * @param lineType   k线类型  选用 ContractKLineConstants 常量
     * @return {@link ContractKLine}
     */
    public ContractKLine selectCurrentKLine(@NotNull String symbolName, @NotNull String lineType) {
        ContractKLineConstants.TimeRangeTypeConfig config = ContractKLineConstants.findByTimeRangeType(lineType);
        try {
            // 读mongoDB 查询对应表最新数据
            assert config != null;
            String collectionName = symbolName + "_" + config.getType();
            Query query = new Query();
            query.with(Sort.by(Sort.Order.desc("endTime")));
            query.skip(0);
            query.limit(1);
            ContractKLineMongo kLineMongo =
                    mongo.findOne(query, ContractKLineMongo.class, collectionName);
            if (kLineMongo != null) {
                ContractKLine kLine = new ContractKLine();
                BeanUtils.copyProperties(kLineMongo, kLine);
                return kLine;
            }
            throw new Exception();
        } catch (Exception e) {
            // mongoDB 如果读不到数据或者没有对应集合报错，都走mysql查询
            try {
                ContractKLine kLine = new ContractKLine();
                kLine.setSymbolName(symbolName);
                kLine.setTableName(config.getTableName());
                return lineMapper.selectCurrentKLine(kLine);
            } catch (Exception exception) {
                e.printStackTrace();
                return null;
            }
        }
    }

    /**
     * 根据userId获取用户合约账户信息
     *
     * @param userId 用户id
     * @return {@link ContractAccount}
     * @since 2022/8/29 15:41
     */
    public ContractAccount getContractAccountByUserId(Long userId) {
        if (Objects.isNull(userId)) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.USER_ID_IS_EMPTY));
        }

        // 组装参数
        WalletContractAccountChangeDTO param = new WalletContractAccountChangeDTO();
        param.setUserId(userId);
        R<ContractAccount> accountR = walletContractAccountControllerFeign.contractInfo(param, SecurityConstants.FROM_IN);
        if (!accountR.getIsSuccess()) {
            log.error("查询用户合约账户信息失败：{}", accountR.getMsg());
            return null;
        }

        ContractAccount data = accountR.getData();
        if (Objects.isNull(data)) {
            return null;
        }

        if (Objects.isNull(data.getMarginBalance())) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.DATA_IS_EMPTY, "marginBalance"));
        }
        if (Objects.isNull(data.getWalletBalance())) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.DATA_IS_EMPTY, "walletBalance"));
        }

        return data;
    }

    /**
     * 根据userId获取用户合约账户信息并锁定
     *
     * @param userId 用户id
     * @return {@link ContractAccount}
     * @since 2022/8/29 15:41
     */
    public ContractAccount getContractAccountByUserIdLock(Long userId) {
        if (Objects.isNull(userId)) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.USER_ID_IS_EMPTY));
        }

        // 组装参数
        WalletContractAccountChangeDTO param = new WalletContractAccountChangeDTO();
        param.setUserId(userId);
        R<ContractAccount> accountR = walletContractAccountControllerFeign.contractInfoLock(param, SecurityConstants.FROM_IN);
        if (!accountR.getIsSuccess()) {
            log.error("查询用户合约账户信息失败：{}", accountR.getMsg());
            return null;
        }

        ContractAccount data = accountR.getData();
        if (Objects.isNull(data)) {
            return null;
        }

        if (Objects.isNull(data.getMarginBalance())) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.DATA_IS_EMPTY, "marginBalance"));
        }
        if (Objects.isNull(data.getWalletBalance())) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.DATA_IS_EMPTY, "walletBalance"));
        }

        return data;
    }


    /**
     * 根据成交记录组装变更
     *
     * @param trade         成交记录
     * @param amount        变更金额
     * @param feeRate       费率
     * @param businessType  变更类型
     * @param operationType 操作类型
     * @param hasFreeze     是否已冻结
     * @return {@link WalletContractAccountChangeDTO}
     * @since 2022/8/30 11:48
     */
    public static WalletContractAccountChangeDTO getChangeAssetParamByTrade(ContractTradeNewBO trade,
                                                                            BigDecimal amount,
                                                                            @Nullable BigDecimal feeRate,
                                                                            WalletContractAccountDetailEnum.BusinessType businessType,
                                                                            WalletContractAccountDetailEnum.OperationType operationType,
                                                                            boolean hasFreeze) {
        return new WalletContractAccountChangeDTO(trade.getUserId(),
                amount,
                feeRate,
                trade.getSymbolId(),
                trade.getSymbolName(),
                businessType,
                operationType,
                hasFreeze);
    }

    /**
     * 根据订单组装变更
     *
     * @param order         订单
     * @param amount        变更金额
     * @param feeRate       费率
     * @param businessType  变更类型
     * @param operationType 操作类型
     * @param hasFreeze     是否已冻结
     * @return {@link WalletContractAccountChangeDTO}
     * @since 2022/8/30 11:10
     */
    public static WalletContractAccountChangeDTO getChangeAssetParamByOrder(ContractOrder order,
                                                                            BigDecimal amount,
                                                                            @Nullable BigDecimal feeRate,
                                                                            WalletContractAccountDetailEnum.BusinessType businessType,
                                                                            WalletContractAccountDetailEnum.OperationType operationType,
                                                                            boolean hasFreeze) {
        return new WalletContractAccountChangeDTO(order.getUserId(),
                amount,
                feeRate,
                order.getSymbolId(),
                order.getSymbolName(),
                businessType,
                operationType,
                hasFreeze);
    }

    /**
     * 根据仓位组装变更
     *
     * @param position      仓位
     * @param amount        变更金额
     * @param feeRate       费率
     * @param businessType  变更类型
     * @param operationType 操作类型
     * @param hasFreeze     是否已冻结
     * @return {@link WalletContractAccountChangeDTO}
     * @since 2022/8/30 11:10
     */
    public static WalletContractAccountChangeDTO getChangeAssetParamByPosition(ContractPosition position,
                                                                               BigDecimal amount,
                                                                               @Nullable BigDecimal feeRate,
                                                                               WalletContractAccountDetailEnum.BusinessType businessType,
                                                                               WalletContractAccountDetailEnum.OperationType operationType,
                                                                               boolean hasFreeze) {
        return new WalletContractAccountChangeDTO(position.getUserId(),
                amount,
                feeRate,
                position.getSymbolId(),
                position.getSymbolName(),
                businessType,
                operationType,
                hasFreeze);
    }

    /**
     * 调用钱包模块
     * <p>修改金额一定要在程序中提前设置好精度，不能超过数据库字段精度，否则会导致金额不准确
     *
     * @param list 参数集合
     * @return {@link R}
     * @since 2022/8/30 11:33
     */
    public R<?> changeAsset(List<WalletContractAccountChangeDTO> list) {
        if (CollectionUtils.isEmpty(list)) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.DATA_IS_EMPTY, "list"));
        }

        return walletContractAccountControllerFeign.change(list, SecurityConstants.FROM_IN);
    }

    /**
     * 根据平仓订单增加仓位平仓中数量
     *
     * @since 2022/9/1 17:39
     */
    public void addPositionClosingNum(Long positionId, BigDecimal num) {
        if (contractPositionMapper.addClosingNum(positionId, num) != 1) {
            // 仓位状态已发生改变
            throw new MqAckException(MessageUtils.get(I18nMessages.POSITION_STATUS_HAS_CHANGED));
        }
    }

    /**
     * 根据平仓订单回滚仓位平仓中数量
     *
     * @since 2022/9/1 17:39
     */
    public void rollbackPositionClosingNumByOrder(ContractOrder order) {
        if (!Objects.equals(ContractOrderEnum.OperationType.CLOSE.getValue(), order.getOperationType())) {
            return;
        }

        if (Objects.isNull(order.getId())) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.OPERATION_FAILURE));
        }
        if (Objects.isNull(order.getPositionId())) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.OPERATION_FAILURE));
        }
        if (Objects.isNull(order.getNoDealNum())) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.OPERATION_FAILURE));
        }

        if (contractPositionMapper.minusClosingNum(order.getPositionId(), order.getNoDealNum()) != 1) {
            throw new RuntimeException(MessageUtils.get(I18nMessages.OPERATION_FAILURE));
        }
    }

    /**
     * 根据平仓订单回滚仓位平仓中数量
     *
     * @param list list
     * @since 2022/9/4 17:26
     */
    public void rollbackPositionClosingNumByOrderList(List<ContractOrder> list) {
        Map<Long, BigDecimal> map = new HashMap<>();
        for (ContractOrder order : list) {
            // 不是平仓，跳过
            if (!Objects.equals(ContractOrderEnum.OperationType.CLOSE.getValue(), order.getOperationType())) {
                continue;
            }

            if (Objects.isNull(order.getPositionId())) {
                throw new RuntimeException(MessageUtils.get(I18nMessages.OPERATION_FAILURE));
            }
            if (Objects.isNull(order.getNoDealNum())) {
                throw new RuntimeException(MessageUtils.get(I18nMessages.OPERATION_FAILURE));
            }

            // 累加金额
            BigDecimal num = map.get(order.getPositionId());
            if (Objects.isNull(num)) {
                num = BigDecimal.ZERO;
            }

            num = num.add(order.getNoDealNum());
            map.put(order.getPositionId(), num);
        }

        // 遍历修改
        map.forEach((positionId, noDealNum) -> {
            if (contractPositionMapper.minusClosingNum(positionId, noDealNum) != 1) {
                throw new RuntimeException(MessageUtils.get(I18nMessages.OPERATION_FAILURE));
            }
        });
    }

    /**
     * 发送全仓计算mq
     *
     * @param userId 用户id
     * @since 2022/9/3 21:49
     */
    public void sendPositionCrossCalculate(Long userId) {
        rabbit.convertAndSend(ContractMqConstants.EXCHANGE_FANOUT_CONTRACT_POSITION_CROSS_CALCULATE,
                null,
                userId);
    }

    /**
     * 发送持久化仓位保证金变动明细消息
     *
     * @param detailList 集合
     * @since 2022/9/3 21:49
     */
    public void sendSavePositionMarginDetailMq(List<ContractPositionMarginDetail> detailList) {
        if (CollectionUtils.isEmpty(detailList)) {
            return;
        }

        rabbit.convertAndSend(ContractMqConstants.EXCHANGE_FANOUT_CONTRACT_POSITION_MARGIN_DETAIL,
                null,
                detailList);
    }

    /**
     * 发送k线成交记录&最新成交记录消息
     *
     * @param bo 成交记录详情
     * @since 2022/9/8 21:49
     */
    public void sendContractInitTradeMq(ContractTradeNewBO bo) {
        ContractKLineTradeCacheMongo vo = new ContractKLineTradeCacheMongo();
        BeanUtils.copyProperties(bo, vo);
        String jsonString = JSONObject.toJSONString(vo);
        rabbit.convertAndSend(ContractMqConstants.EXCHANGE_FANOUT_CONTRACT_INIT_TRADE,
                null,
                jsonString);
    }

    /**
     * 发送资金费率任务消息
     *
     * @since 2022/9/23 11:01
     */
    public void sendRateSettlementJobMq(RateSettlementJobBO bo) {
        String jsonString = JSONObject.toJSONString(bo);
        rabbit.convertAndSend(ContractMqConstants.EXCHANGE_FANOUT_RATE_SETTLEMENT_JOB,
                null,
                jsonString);
    }

    /**
     * 发送订单延迟过期消息
     *
     * @param orderId 订单id
     * @param userId  用户id
     * @since 2022/9/3 21:49
     */
    public void sendLimitOrderExpire(Long orderId, Long userId) {
        ContractOrderCancelDTO dto = new ContractOrderCancelDTO();
        dto.setId(orderId);
        dto.setUserId(userId);

        rabbit.convertAndSend(ContractMqConstants.EXCHANGE_FANOUT_DELAY_CONTRACT_ORDER_EXPIRE,
                null,
                dto);
    }

    /**
     * 发送深度图消息
     *
     * @param exchange   交换机名称
     * @param routingKey key
     * @param side       方向（0做多、1做空）
     * @param symbolName 币对名称
     * @param num        数量
     * @param price      价格
     * @since 2022/9/6 11:08
     */
    public void sendDepthMap(String exchange,
                             String routingKey,
                             String side,
                             String symbolName,
                             BigDecimal num,
                             BigDecimal price) {
        // 发送深度图参数消息
        RobotDepthMapMqBO bo = new RobotDepthMapMqBO();
        bo.setSymbolName(symbolName);
        bo.setSide(side);
        bo.setNum(num);
        bo.setPrice(price);
        rabbit.convertAndSend(exchange,
                routingKey,
                bo);
    }

    /**
     * 发送强平处理消息
     *
     * @param positionId 仓位id
     * @since 2022/9/6 11:08
     */
    public void sendLiquidateHandleMq(Long positionId) {
        rabbit.convertAndSend(ContractMqConstants.EXCHANGE_FANOUT_CONTRACT_POSITION_LIQUIDATE_HANDLE,
                null,
                positionId);
    }

    /**
     * 发送深度图消息
     *
     * @param exchange   交换机名称
     * @param routingKey key
     * @param order      订单
     * @since 2022/9/6 11:08
     */
    public void sendDepthMap(String exchange,
                             String routingKey,
                             ContractOrder order) {
        // 发送深度图参数消息
        RobotDepthMapMqBO bo = new RobotDepthMapMqBO();
        bo.setSymbolName(order.getSymbolName());
        bo.setSide(order.getSide());
        bo.setNum(order.getNoDealNum());
        bo.setPrice(order.getPrice());
        rabbit.convertAndSend(exchange,
                routingKey,
                bo);
    }

    /**
     * 获取未实现盈亏
     * <p>公式：
     * <p>做多收益 = (标记价 - 开仓均价) * 持仓数量
     * <p>做空收益 = (开仓均价 - 标记价) * 持仓数量
     */
    public void computeFloatProfit(AppContractCurrentPositionVO vo) {
        ContractSymbolMongo symbol = getSymbolFromThread(vo.getSymbolName());
        if (Objects.isNull(symbol.getMarkPrice())) {
            return;
        }

        // 标记价
        BigDecimal markPrice = symbol.getMarkPrice();
        vo.setMarkPrice(markPrice);
        // 持仓量(USDT)
        vo.setUNowNum(vo.getNowNum().multiply(markPrice));

        // 未实现盈亏
        BigDecimal profit = OrderCalculator.getProfit(markPrice, vo.getOpenAvgPrice(), vo.getNowNum(), vo.getSide());
        // 盈亏率
        BigDecimal profitRate = OrderCalculator.getProfitRate(vo.getOpenAvgPrice(), vo.getMarkPrice(), vo.getMultiple(), vo.getSide())
                .multiply(BigDecimalConstants.HUNDRED);

        vo.setFloatProfit(profit);
        vo.setPositionProfitRate(profitRate);
    }

    /**
     * 计算保证金率
     * 公式：
     * 逐仓 : 计算盈亏后保证金 - (标准保证金 * 强平提前量) / 标准保证金
     * 全仓 : (计算盈亏后保证金 + 合约账户可用金额) - (标准保证金 * 强平提前量)  / 标准保证金
     */
    public void computeMarginRate(AppContractCurrentPositionVO vo) {
        // 获取币对信息
        ContractSymbolMongo symbol = getSymbolFromThread(vo.getSymbolName());
        // 仓位价值
        BigDecimal positionValue = OrderCalculator.getOrderValue(vo.getOpenAvgPrice(), vo.getNowNum());
        // 标准保证金
        BigDecimal standardMargin = PositionCalculator.getStandardMargin(positionValue, vo.getMultiple());
        vo.setInitialMargin(standardMargin);

        // 全仓
        if (Objects.equals(ContractPositionEnum.Mode.FULL.getValue(), vo.getMode())) {
            // 根据标记价计算持仓价值
            BigDecimal positionValueByMarkPrice = OrderCalculator.getOrderValue(symbol.getMarkPrice(), vo.getNowNum());
            // 根据标记价计算显示的持仓保证金
            BigDecimal showMargin = PositionCalculator.getStandardMargin(positionValueByMarkPrice, vo.getMultiple());
            vo.setMargin(showMargin);
            // 保证金率
            vo.setMarginRate(vo.getMarginRate().multiply(BigDecimalConstants.HUNDRED));

            // 逐仓计算保证金率
        } else {
            // 有效保证金
            BigDecimal effectiveMargin = PositionCalculator.getEffectiveMargin(vo.getOpenAvgPrice(), vo.getLiquidatePrice(), vo.getSide(), vo.getNowNum());
            BigDecimal marginRate = PositionCalculator.getMarginRate(effectiveMargin,
                    vo.getFloatProfit(),
                    standardMargin);
            vo.setMarginRate(marginRate.multiply(BigDecimalConstants.HUNDRED));
        }
    }

    /**
     * 平仓时，根据仓位信息初始化新增订单信息
     *
     * @param newDTO   newDTO
     * @param position 仓位信息
     * @since 2022/9/6 17:00
     */
    public static void initOrderByPositionWhenClose(ContractOrderNewDTO newDTO, ContractPosition position) {
        newDTO.setUserId(position.getUserId());
        newDTO.setSymbolId(position.getSymbolId());
        newDTO.setPositionMode(position.getMode());
        newDTO.setSide(position.getSide());
        newDTO.setOperationType(ContractOrderEnum.OperationType.CLOSE.getValue());
        newDTO.setIsRobot(CommonDictEnum.YesOrNo.NO.getValue());
        newDTO.setMultiple(position.getMultiple());
    }

    /**
     * 根据币对id和杠杆倍数 获取持仓上下限
     *
     * @param dto 请求参数
     * @return 响应结果
     */
    public ContractHierarchyVO getHierarchy(ContractHierarchyDTO dto) {
        try {
            // 先读mongo缓存,查不到读mysql
            ContractSymbolHierarchyMongo mongoOne = mongo.findOne(Query.query(Criteria
                    .where("symbolId").is(dto.getSymbolId())
                    .and("startMultiple").lte(dto.getMultiple())
                    .and("endMultiple").gte(dto.getMultiple())
            ), ContractSymbolHierarchyMongo.class);
            if (mongoOne != null) {
                ContractHierarchyVO vo = new ContractHierarchyVO();
                BeanUtils.copyProperties(mongoOne, vo);
                return vo;
            }
            throw new Exception();
        } catch (Exception e) {
            QueryWrapper<RippleContractSymbolHierarchy> wrapper = new QueryWrapper<>();
            wrapper.eq("symbol_id", dto.getSymbolId());
            wrapper.le("start_multiple", dto.getMultiple());
            wrapper.ge("end_multiple", dto.getMultiple());
            RippleContractSymbolHierarchy hierarchy = hierarchyMapper.selectOne(wrapper);
            if (hierarchy != null) {
                ContractHierarchyVO vo = new ContractHierarchyVO();
                BeanUtils.copyProperties(hierarchy, vo);
                return vo;
            }

            throw new MqNotAckException("未获得可用的头寸信息");
        }
    }

    /**
     * 根据头寸校验总持仓数量
     *
     * @param hierarchy        头寸
     * @param positionTotalNum 总持仓数量
     * @param multiple         杠杆倍数
     * @param symbolName       币对名称
     * @since 2022/9/11 19:06
     */
    public static void checkHierarchy(ContractHierarchyVO hierarchy, double positionTotalNum, Integer multiple, String symbolName) {
        // 最小持仓数量目前不需要校验
        // 最大持仓数量
        if (!Objects.isNull(hierarchy.getMaxPosition())) {
            if (hierarchy.getMaxPosition().compareTo(BigDecimal.valueOf(positionTotalNum)) < 0) {
                throw new MqNotAckException(MessageUtils.get(I18nMessages.WHEN_THE_LEVERAGE_RATIO_IS_S_MAXIMUM_NUMBER_OF_POSITIONS_HELD_IS_S,
                        multiple,
                        hierarchy.getMaxPosition(),
                        symbolName));
            }
        }
    }

    /**
     * 根据币对信息设置对象中BigDecimal字段精度
     * <p>使用后需要调用ContractUtil.removeSymbolThreadCache()
     *
     * @param obj        对象
     * @param symbolName 币对名称
     * @since 2022/9/12 10:38
     */
    public void setScaleBySymbol(Object obj, String symbolName) {
        List<Field> fieldList = ReflectUtil.getFiledAsList(obj.getClass());

        // 遍历属性
        fieldList.forEach(field -> {
            // 检查是否有@SetScaleMainCurrency、@SetScaleTokenCurrency注解
            boolean needSetMain = field.isAnnotationPresent(SetScaleMainCurrency.class);
            boolean needSetToken = field.isAnnotationPresent(SetScaleTokenCurrency.class);
            if (!(needSetMain || needSetToken)) {
                return;
            }

            try {
                // 获取值，判断是否为空
                Method getter = ReflectUtil.getGetter(obj, field.getName());
                Object objValue = getter.invoke(obj);
                if (Objects.isNull(objValue)) {
                    return;
                }

                // 只处理BigDecimal类型字段
                if (!(objValue instanceof BigDecimal)) {
                    return;
                }

                // 如果不为空，强转为BigDecimal，根据注解配置设置精度
                BigDecimal value = (BigDecimal) objValue;
                Method setter = ReflectUtil.getSetter(obj, field.getName(), field.getType());
                if (needSetMain) {
                    SetScaleMainCurrency anno = field.getAnnotation(SetScaleMainCurrency.class);

                    int scale;
                    // 设置了阈值上限 && 值大于阈值上限
                    if (anno.zeroScaleWhenGte() > -1 && value.compareTo(BigDecimal.valueOf(anno.zeroScaleWhenGte())) > 0) {
                        scale = 0;
                    } else if (anno.scale() > -1) {
                        scale = anno.scale();
                    } else {
                        ContractSymbolMongo symbol = getSymbolFromThread(symbolName);
                        scale = symbol.getMainCurAccuracy();
                    }

                    value = value.setScale(scale, anno.mode());

                } else {
                    SetScaleTokenCurrency anno = field.getAnnotation(SetScaleTokenCurrency.class);

                    int scale;
                    // 设置了阈值上限 && 值大于阈值上限
                    if (anno.zeroScaleWhenGte() > -1 && value.compareTo(BigDecimal.valueOf(anno.zeroScaleWhenGte())) > 0) {
                        scale = 0;
                    } else if (anno.scale() > -1) {
                        scale = anno.scale();
                    } else {
                        ContractSymbolMongo symbol = getSymbolFromThread(symbolName);
                        scale = symbol.getTokenCurAccuracy();
                    }
                    value = value.setScale(scale, anno.mode());
                }
                DecimalFormat format = new DecimalFormat();
                // 设置值
                setter.invoke(obj, value);
            } catch (IllegalAccessException | InvocationTargetException e) {
                throw new RuntimeException(e);
            }
        });
    }

    /**
     * 根据币对名称查询当前线程中缓存的币对信息
     * <p>使用后需要调用ContractUtils.removeSymbolThreadCache()
     *
     * @param symbolName 币对名称
     * @return {@link ContractSymbolMongo}
     * @since 2022/9/12 09:55
     */
    public ContractSymbolMongo getSymbolFromThread(String symbolName) {
        Map<String, ContractSymbolMongo> symbolMap = ContractUtils.symbolMap.get();
        // 如果为空，初始化
        if (Objects.isNull(symbolMap)) {
            symbolMap = new HashMap<>();
            ContractUtils.symbolMap.set(symbolMap);
        }

        ContractSymbolMongo cache = symbolMap.get(symbolName);
        if (!Objects.isNull(cache)) {
            return cache;
        }

        // 如果JVM缓存中没有，从mongo中查询
        ContractSymbolMongo symbol = getSymbolByName(symbolName);
        symbolMap.put(symbol.getName(), symbol);
        return symbol;
    }

    /**
     * 清空symbolMap
     *
     * @since 2022/9/12 14:57
     */
    public static void removeSymbolThreadCache() {
        symbolMap.remove();
    }

    /**
     * 清空symbolMap
     *
     * @since 2022/9/12 14:57
     */
    public static WalletContractAccountDetailEnum.OperationType getProfitOperationType(BigDecimal profit) {
        return profit.compareTo(BigDecimal.ZERO) > 0 ?
                WalletContractAccountDetailEnum.OperationType.ADD
                : WalletContractAccountDetailEnum.OperationType.MINUS;
    }

    /**
     * 发送仓位强平邮件
     *
     * @param dto 参数
     * @since 2022/9/14 16:02
     */
    public void sendLiquidateEmail(ContractPositionLiquidateEmailDTO dto) throws MessagingException {
        if (Objects.isNull(dto.getUserId())) {
            return;
        }

        // 查询用户信息
        User user = getUserById(dto.getUserId());
        // User user = new User();
        // user.setEmail("1036532868@qq.com");
        // user.setLanguageCode("zh_CN");
        // user.setLanguageCode("zh_TW");
        // user.setLanguageCode("en_US");
        if (Objects.isNull(user)) {
            return;
        }

        contractEmailSender.sendLiquidate(dto, user);
    }

    /**
     * 发送仓位强平邮件消息
     *
     * @param userId       用户id
     * @param symbolName   币对名称
     * @param positionMode 仓位模式
     * @param side         方向
     * @param multiple     杠杆倍数
     * @param markPrice    标记价
     * @since 2022/9/27 14:40
     */
    public void sendLiquidateEmailMq(Long userId,
                                     String symbolName,
                                     String positionMode,
                                     String side,
                                     Integer multiple,
                                     BigDecimal markPrice) {
        ContractPositionLiquidateEmailDTO dto = new ContractPositionLiquidateEmailDTO();
        dto.setUserId(userId);
        dto.setSymbolName(symbolName);
        dto.setPositionMode(positionMode);
        dto.setSide(side);
        dto.setMultiple(multiple);
        dto.setMarkPrice(markPrice);

        rabbit.convertAndSend(ContractMqConstants.EXCHANGE_FANOUT_CONTRACT_EMAIL_LIQUIDATE,
                null,
                dto);
    }

    /**
     * 发送需要追加保证金邮件
     *
     * @param dto 参数
     * @since 2022/9/14 16:02
     */
    public void sendNeedAppendMarginEmail(ContractPositionNeedAppendMarginEmailDTO dto) throws MessagingException {
        if (Objects.isNull(dto.getUserId())) {
            return;
        }

        // 查询用户信息
        User user = getUserById(dto.getUserId());
        // User user = new User();
        // user.setEmail("1036532868@qq.com");
        // user.setLanguageCode("zh_CN");
        // user.setLanguageCode("zh_TW");
        // user.setLanguageCode("en_US");
        if (Objects.isNull(user)) {
            return;
        }

        contractEmailSender.sendNeedAppendMargin(dto, user);
    }

    /**
     * 发送需要追加保证金邮件
     *
     * @param dtoList 参数集合
     * @since 2022/9/14 16:02
     */
    public void sendNeedAppendMarginEmailMq(List<ContractPositionNeedAppendMarginEmailDTO> dtoList) {
        if (CollectionUtils.isEmpty(dtoList)) {
            return;
        }

        rabbit.convertAndSend(ContractMqConstants.EXCHANGE_FANOUT_CONTRACT_EMAIL_MARGIN_CHECK,
                null,
                dtoList);
    }

    /**
     * 发送全仓自动追加保证金消息
     *
     * @param dto dto
     * @since 2022/9/16 15:02
     */
    public void sendPositionMarginAppendMq(ContractPositionMarginAppendDTO dto) {
        rabbit.convertAndSend(ContractMqConstants.EXCHANGE_FANOUT_CONTRACT_POSITION_MARGIN_APPEND,
                null,
                dto);
    }

    /**
     * 根据订单操作类型获得保证金明细业务类型
     *
     * @param orderOperationType 订单操作类型
     * @return {@link ContractPositionMarginDetailEnum.BusinessType}
     * @since 2022/9/16 18:59
     */
    public static ContractPositionMarginDetailEnum.BusinessType getMarginDetailBusinessTypeByOrderOperationType(String orderOperationType) {
        return ContractOrderEnum.OperationType.OPEN.getValue().equals(orderOperationType) ?
                ContractPositionMarginDetailEnum.BusinessType.OPEN_POSITION
                : ContractPositionMarginDetailEnum.BusinessType.CLOSE_POSITION;
    }

    /**
     * 根据订单操作类型获得保证金明细操作类型
     *
     * @param orderOperationType 订单操作类型
     * @return {@link ContractPositionMarginDetailEnum.BusinessType}
     * @since 2022/9/16 18:59
     */
    public static ContractPositionMarginDetailEnum.OperationType getMarginDetailOperationTypeByOrderOperationType(String orderOperationType) {
        return ContractOrderEnum.OperationType.OPEN.getValue().equals(orderOperationType) ?
                ContractPositionMarginDetailEnum.OperationType.ADD
                : ContractPositionMarginDetailEnum.OperationType.MINUS;
    }

    /**
     * 发送历史订单迁移消息
     *
     * @param orderIdList 订单idList
     * @since 2022/9/20 17:14
     */
    public void sendOrderMigrate(List<Long> orderIdList) {
        if (CollectionUtils.isEmpty(orderIdList)) {
            return;
        }

        rabbit.convertAndSend(ContractMqConstants.EXCHANGE_FANOUT_CONTRACT_ORDER_MIGRATE,
                null,
                orderIdList);
    }

    /**
     * 发送历史仓位迁移消息
     *
     * @param positionIdList 仓位idList
     * @since 2022/9/20 17:14
     */
    public void sendPositionMigrate(List<Long> positionIdList) {
        if (CollectionUtils.isEmpty(positionIdList)) {
            return;
        }

        rabbit.convertAndSend(ContractMqConstants.EXCHANGE_FANOUT_CONTRACT_POSITION_MIGRATE,
                null,
                positionIdList);
    }

    /**
     * 发送历史止盈止损迁移消息
     *
     * @param stopOrderIdList 止盈止损idList
     * @since 2022/9/20 17:14
     */
    public void sendStopOrderMigrate(List<Long> stopOrderIdList) {
        if (CollectionUtils.isEmpty(stopOrderIdList)) {
            return;
        }

        rabbit.convertAndSend(ContractMqConstants.EXCHANGE_FANOUT_CONTRACT_STOP_ORDER_MIGRATE,
                null,
                stopOrderIdList);
    }

    /**
     * 计算时根据方向获取对应的数值
     *
     * @param side 方向
     * @return {@link BigDecimal}
     * @since 2022/10/12 14:41
     */
    public static BigDecimal getSideNum(String side) {
        // 做多
        if (Objects.equals(ContractOrderEnum.Side.MORE.getValue(), side)) {
            return BigDecimal.ONE;

        } else if (Objects.equals(ContractOrderEnum.Side.EMPTY.getValue(), side)) {
            // 做空
            return BigDecimalConstants.NEGATIVE_ONE;

        } else {
            throw new MqNotAckException("无法识别的方向");
        }
    }

    /**
     * 记录最新强平订单信息
     *
     * @param symbolName 币对名称
     * @param mode       仓位模式
     * @param side       方向
     * @param multiple   杠杆倍数
     * @param markPrice  标记价
     */
    public void recordNewestLiquidateOrder(String symbolName, String mode, String side, Integer multiple, BigDecimal markPrice) {
        try {
            ContractLiquidateOrderVO vo = new ContractLiquidateOrderVO();
            vo.setSymbolName(symbolName);
            vo.setMode(mode);
            vo.setSide(side);
            vo.setMultiple(multiple);
            vo.setPrice(markPrice);
            redis.opsForValue().set(symbolName + OpenApiRedisConstantsV1.NEWEST_LIQUIDATE_ORDER, vo);
        } catch (Exception e) {
            log.error("记录最新强平订单信息 失败");
            e.printStackTrace();
        }
    }

    /**
     * 获取币对最新强平订单信息
     *
     * @param symbolName 币对名称
     * @return r
     */
    public ContractLiquidateOrderVO getNewestLiquidateOrder(String symbolName) {
        try {
            return (ContractLiquidateOrderVO) redis.opsForValue().get(symbolName + OpenApiRedisConstantsV1.NEWEST_LIQUIDATE_ORDER);
        } catch (Exception e) {
            throw OpenApiUtil.getError(AbnormalEnum.MSG.DISCONNECTED);
        }
    }
}
